package main

/**
 * 现有的问题
 * 1. gorm处理一对多和多对多表
 */

import (
	"bytes"
	"context"
	"crypto/sha512"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"runtime"
	"strconv"
	"sync"
	"syscall"
	"time"

	"github.com/dgrijalva/jwt-go"
	_ "github.com/go-sql-driver/mysql"
	"github.com/go-xorm/core"
	"github.com/go-xorm/xorm"
	"github.com/gorilla/sessions"
	"github.com/labstack/echo"
	"github.com/labstack/echo-contrib/session"
	"github.com/labstack/echo/middleware"
	// "github.com/mia0x75/govalidator"
	"github.com/mia0x75/mac/config"
	"github.com/mia0x75/mac/hack"
	"github.com/mia0x75/mac/models"
	"github.com/mia0x75/mac/utils"
	"github.com/patrickmn/go-cache"
)

var (
	engine  *xorm.Engine
	cfg     *config.AppConfig
	lock    *sync.Mutex  = new(sync.Mutex)
	caches  *cache.Cache = cache.New(5*time.Minute, 10*time.Minute)
	format  string       = `%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local`
	masters Masters
	path    string
)

const (
	JWT_ContextKey = "user"
	JWT_AuthScheme = "Token"

	PAGE_SIZE = 15
)

type Masters []*Master

type Database struct {
	Name string
}

type Table struct {
	Name string
	Type string `json:"-"`
}

type Column struct {
	Field      string
	Type       string
	Collation  string
	Null       string
	Key        string
	Default    string
	Extra      string
	Privileges string `json:"-"`
	Comment    string
}

type Index struct {
	Table        string
	NonUnique    bool
	KeyName      string
	SeqInIndex   uint
	ColumnName   string
	Collation    string `json:"-"`
	Cardinality  uint   `json:"-"`
	SubPart      string `json:"-"`
	Packed       string `json:"-"`
	Null         string
	IndexType    string
	Comment      string
	IndexComment string
}

type Master struct {
	Engine   *xorm.Engine `json:"-"`
	MasterId uint         `json:"MasterId"`
	Host     uint         `json:"Host"`
	Port     uint16       `json:"Port"`
	User     []byte       `json:"-"`
	Password []byte       `json:"-"`
	Status   uint8        `json:"Status"`
	Slaves   []Slave      `json:"-"`
}

type Slave struct {
	Engine   *xorm.Engine `json:"-"`
	SlaveId  uint         `json:"SlaveId"`
	MasterId uint         `json:"MasterId"`
	Host     uint         `json:"Host"`
	Port     uint16       `json:"Port"`
	User     []byte       `json:"-"`
	Password []byte       `json:"-"`
	Status   uint8        `json:"Status"`
}

func (m *Master) databases() ([]*Database, error) {
	key := fmt.Sprintf("databases-on-master-%d", m.MasterId)
	if databases, found := caches.Get(key); found {
		if rs, ok := databases.([]*Database); ok {
			return rs, nil
		}
	}
	rs := make([]*Database, 0)
	m.Engine.Ping()
	rows, err := m.Engine.QueryString("SHOW DATABASES;")
	if err != nil {
		return nil, err
	}
	for _, row := range rows {
		if row["Database"] == "information_schema" || row["Database"] == "performance_schema" {
			continue
		}
		rs = append(rs, &Database{
			Name: row["Database"],
		})
	}
	caches.Set(key, rs, cache.NoExpiration)
	return rs, nil
}

func (m *Master) tables(database string) ([]*Table, error) {
	key := fmt.Sprintf("tables-for-database-%s-on-master-%d", database, m.MasterId)
	if tables, found := caches.Get(key); found {
		if rs, ok := tables.([]*Table); ok {
			return rs, nil
		}
	}
	rs := make([]*Table, 0)
	m.Engine.Ping()
	rows, err := m.Engine.QueryString(fmt.Sprintf("SHOW FULL TABLES FROM `%s` WHERE TABLE_TYPE = 'BASE TABLE';", database))
	if err != nil {
		return nil, err
	}
	for _, row := range rows {
		rs = append(rs, &Table{
			Name: row[fmt.Sprintf("Tables_in_%s", database)],
			Type: row["Table_type"],
		})
	}
	caches.Set(key, rs, cache.NoExpiration)
	return rs, nil
}

func (m *Master) columns(database, table string) ([]*Column, error) {
	key := fmt.Sprintf("columns-for-table-%s.%s-on-master-%d", database, table, m.MasterId)
	if columns, found := caches.Get(key); found {
		if rs, ok := columns.([]*Column); ok {
			return rs, nil
		}
	}
	rs := make([]*Column, 0)
	m.Engine.Ping()
	rows, err := m.Engine.QueryString(fmt.Sprintf("SHOW FULL COLUMNS FROM `%s` FROM `%s`;", table, database))
	if err != nil {
		return nil, err
	}
	for _, row := range rows {
		rs = append(rs, &Column{
			Field:      row["Field"],
			Type:       row["Type"],
			Collation:  row["Collation"],
			Null:       row["Null"],
			Key:        row["Key"],
			Default:    row["Default"],
			Extra:      row["Extra"],
			Privileges: row["Privileges"],
			Comment:    row["Comment"],
		})
	}
	caches.Set(key, rs, cache.NoExpiration)
	return rs, nil
}

func (m *Master) indices(database, table string) ([]*Index, error) {
	key := fmt.Sprintf("indices-for-table-%s.%s-on-master-%d", database, table, m.MasterId)
	if indices, found := caches.Get(key); found {
		if rs, ok := indices.([]*Index); ok {
			return rs, nil
		}
	}
	rs := make([]*Index, 0)
	m.Engine.Ping()
	rows, err := m.Engine.QueryString(fmt.Sprintf("SHOW INDEX FROM `%s` FROM `%s`;", table, database))
	if err != nil {
		return nil, err
	}
	for _, row := range rows {
		seqInIndex, _ := strconv.ParseUint(row["SeqInIndex"], 10, 32)
		cardinality, _ := strconv.ParseUint(row["Cardinality"], 10, 32)
		nonUnique, _ := strconv.ParseBool(row["NonUnique"])
		rs = append(rs, &Index{
			Table:        row["Table"],
			NonUnique:    nonUnique,
			KeyName:      row["KeyName"],
			SeqInIndex:   uint(seqInIndex),
			ColumnName:   row["ColumnName"],
			Collation:    row["Collation"],
			Cardinality:  uint(cardinality),
			SubPart:      row["SubPart"],
			Packed:       row["Packed"],
			Null:         row["Null"],
			IndexType:    row["IndexType"],
			Comment:      row["Comment"],
			IndexComment: row["IndexComment"],
		})
	}
	caches.Set(key, rs, cache.NoExpiration)
	return rs, nil
}

//                  _
//  _ __ ___  _ __ | | ___
// | '_ ` _ \| '_ \| |/ _ \
// | | | | | | |_) | |  __/
// |_| |_| |_| .__/|_|\___|
//           |_|
//

// mode 1:
// gh-ost -user=ghost -port=3307 -password=ghost123 -host=192.168.40.134 -allow-on-master \
//  -max-load='Threads_running=100,Threads_connected=500' -initially-drop-old-table \
//  -initially-drop-ghost-table  -exact-rowcount=false -database=ghost -table=tableName \
//  -alter=" ADD ghostc1 VARCHAR(20) DEFAULT '' COMMENT '主库模式测试'" -execute \
//  -throttle-additional-flag-file=/tmp/gh-ost.sock
// =========================================================================================
// mode 2:
// gh-ost -user=ghost -port=3307 -password=ghost123 -host=192.168.40.135 -allow-on-master \
//  -max-load='Threads_running=100,Threads_connected=500' -initially-drop-old-table \
//  -initially-drop-ghost-table   -exact-rowcount=false -database=ghost -table=tableName \
//  -alter=" ADD ghostc2 VARCHAR(20) DEFAULT '' COMMENT '从库模式测试'" -execute \
//  -throttle-additional-flag-file=/tmp/gh-ost.sock
func init() {
	// parse configuration file
	p, err := utils.GetCurrentPath()
	if err != nil {
		fmt.Printf("cannot startup project, error: %s\r\n", err.Error())
		os.Exit(1)
	} else {
		path = p
		c, err := config.ParseConfigFile(path + "/config.yaml")
		if err != nil {
			fmt.Printf("config file error:%s\r\n", err.Error())
			os.Exit(1)
		}
		cfg = c
	}

	dns := fmt.Sprintf(format, cfg.Database.User, cfg.Database.Password, cfg.Database.Host, cfg.Database.Port, cfg.Database.Db)
	engine, err = xorm.NewEngine("mysql", dns)
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}

	engine.Logger().SetLevel(core.LOG_ERR)
	engine.Ping()
	engine.SetMaxIdleConns(5)
	engine.SetMaxOpenConns(10)
	if cfg.Debug {
		engine.ShowSQL(true)
	} else {
		engine.ShowSQL(false)
	}

	// 初始化masters
	mdlMasters := make([]models.Master, 0)
	engine.Find(&mdlMasters)

	for _, s := range mdlMasters {
		m := &Master{
			Engine:   nil,
			MasterId: s.MasterId,
			Host:     s.Host,
			Port:     s.Port,
			User:     s.User,
			Password: s.Password,
			Status:   s.Status,
		}
		masters = append(masters, m)
		// TODO: 获取所有的从库信息
		// var slaves []models.Slave
		// DB.Model(&s).Association("Slave").Find(&slaves)
		// for _, s := range s.Slaves {
		// 	fmt.Println(fmt.Sprintf("└── slave: %3d - %10d - %5d - %s - %s - %d", s.SlaveId, s.Host, s.Port, s.User, s.Password, s.Status))
		// }
	}

	ch := make(chan *Master, 64)
	var wg sync.WaitGroup
	cpu := runtime.NumCPU()
	for i := 0; i < cpu; i++ {
		wg.Add(1)
		go func() {
			for item := range ch {
				if item.Status != 1 {
					continue
				}
				key := []byte(cfg.Key)
				user, _ := utils.DecryptCBC(item.User, key)
				password, _ := utils.DecryptCBC(item.Password, key)
				host := utils.Inet_ntoa(item.Host)
				dns := fmt.Sprintf(format, user, password, host, item.Port, "mysql")
				db, err := xorm.NewEngine("mysql", dns)
				if err != nil {
					fmt.Printf("%v", err)
					continue
				} else {
					db.Logger().SetLevel(core.LOG_ERR)
					if cfg.Debug {
						db.ShowSQL(true)
					} else {
						db.ShowSQL(false)
					}
					item.Engine = db
				}
			}
			wg.Done()
		}()
	}

	for _, m := range masters {
		ch <- m
	}

	// Finish like this
	close(ch)
	wg.Wait()
}

func main() {
	// 初始化打开，主函数结束关闭
	defer engine.Close()

	fmt.Println(fmt.Sprintf("git commit: %s", hack.Version))
	fmt.Println(fmt.Sprintf("build time: %s", hack.Compile))
	runtime.GOMAXPROCS(runtime.NumCPU())

	// 初始化服务器
	e := echo.New()
	e.HidePort = true
	e.HideBanner = true
	e.Debug = false
	e.HTTPErrorHandler = func(err error, c echo.Context) {
		var code = http.StatusInternalServerError
		if he, ok := err.(*echo.HTTPError); ok {
			code = he.Code
		}
		if !c.Response().Committed {
			err = c.NoContent(code)
		}
	}

	// 处理中间件
	e.Pre(middleware.RemoveTrailingSlash())
	e.Use(middleware.Recover())
	e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
		Level: 5,
	}))
	// e.Use(middleware.CSRF()) // TODO: 403
	e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
		Format: `time:${time_unix} remote_ip:${remote_ip} host:${host} ` +
			`method:${method} uri:${uri} status:${status} bytes_in:${bytes_in} ` +
			`bytes_out:${bytes_out}` + "\n",
	}))
	e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret"))))

	// CORS restricted
	// Allows requests from any `https://labstack.com` or `https://labstack.net` origin
	// wth GET, PUT, POST or DELETE method.
	// e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
	// 	AllowOrigins: []string{"https://labstack.com", "https://labstack.net"},
	// 	AllowMethods: []string{echo.GET, echo.PUT, echo.POST, echo.DELETE},
	// }))

	// e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
	// 	TokenLookup: "header:X-XSRF-TOKEN",
	// }))

	// 处理静态资源
	e.Static("/assets", "public/assets")
	e.Static("/vendors", "public/vendors")

	// 处理路由
	// 静态路由，处理html页面，表现层和服务端分离，采用异步请求
	e.File("/", "public/index.html")
	e.File("/favicon.ico", "public/assets/images/favicon.ico")

	// Login route
	e.POST("/user-login", userLogin)
	e.POST("/token-validation", tokenValidation)

	r := e.Group("/api")
	// Default ContextKey for JWT is user
	// Retreive context via c.GET("user")
	r.Use(middleware.JWTWithConfig(middleware.JWTConfig{
		SigningKey: []byte(cfg.Secret),
		ContextKey: JWT_ContextKey,
		AuthScheme: JWT_AuthScheme,
	}))

	h := func(echo.Context) error { return nil }
	r.GET("/masters", h, showMasters)
	r.GET("/masters/:id/databases", h, showDatabases)
	r.GET("/masters/:id/databases/:database/tables", h, showTables)
	r.GET("/masters/:id/databases/:database/tables/:table/columns", h, showColumns)
	r.GET("/masters/:id/databases/:database/tables/:table/indices", h, showIndices)

	r.GET("/users/:id/tickets", h, func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
			token := c.Get("user").(*jwt.Token)
			claims := token.Claims.(jwt.MapClaims)
			// id = token.id or toke.roles include admin
			if float64(id) == claims["id"].(float64) {
				var tickets []models.Ticket
				engine.Ping()
				engine.Where("user_id = ?", uint(id)).Find(&tickets)
				c.JSON(200, tickets)
			}
			return nil
		}
	})

	r.GET("/users/:id/reviewed-tickets", h, func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			return nil
		}
	})

	r.GET("/users/:id/reviewing-tickets", h, func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			return nil
		}
	})

	e.POST("/update", update)

	// r.GET("/servers/:id/queries", runQueries)

	// 应用程序接口部分路由
	// r.POST("/users",           createUser)
	// r.GET("/users",            getUsers)
	// r.GET("/users/:id",        getUser)
	// r.DELETE("/users/:id",     deleteUser)
	// r.PUT("/users/:id",        updateUser)

	// r.GET("/users/:id/reviewers", getUser)

	// r.POST("/users/:id/roles",   getUser)
	// r.GET("/users/:id/roles",    getUser)
	// r.DELETE("/users/:id/roles", getUser)
	// r.PUT("/users/:id/roles",    getUser)

	// r.POST("/masters",         createMaster)
	// r.GET("/masters",          getMasters)
	// r.GET("/masters/:id",      getMaster)
	// r.DELETE("/masters/:id",   deleteMaster)
	// r.PUT("/masters/:id",      updateMaster)

	// r.POST("/tickets",         createTicket)
	// ?uid=999&status=99
	r.GET("/tickets", h, func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			if value, err := getOptionalSoleRequestValue(c.QueryParams(), "offset", "0"); err == nil {
				if offset, err := strconv.ParseInt(value, 10, 64); err == nil {
					var tickets []models.Ticket
					engine.Ping()
					engine.Where("ticket_id > ?", offset).Limit(PAGE_SIZE).Find(&tickets)
					return c.JSON(200, tickets)
				}
			}
			return echo.NewHTTPError(400, "Bad Request")
		}
	})
	// r.GET("/tickets/:id",      getTicket)
	// r.DELETE("/tickets/:id",   deleteTicket)
	// r.PUT("/tikets/:id",       updateTicket)

	// r.GET("/tickets/:id/approve", getTickets)

	// r.POST("/relations",       createRelation)
	// r.GET("/relations",        getRelations)
	// r.GET("/relations/:id",    getRelation)
	// r.DELETE("/relations/:id", deleteRelation)
	// r.PUT("/relations/:id",    updateRelation)

	// r.POST("/slaves",          createRelation)
	// r.GET("/slaves/:id",       getRelations)
	// r.GET("/slaves/:id",       getRelation)
	// r.DELETE("/slaves/:id",    deleteRelation)
	// r.PUT("/slaves/:id",       updateRelation)

	// r.GET("/taxonomies",       getTaxonomies)

	// r.GET("/options",          getOptions)
	// r.GET("/options/:id",      getOption)

	// r.GET("/roles",            getRoles)

	// r.GET("/rules",            getRules)

	// 启动服务
	addr := fmt.Sprintf("%s:%d", cfg.Listen, cfg.Port)
	go func() {
		err := e.StartTLS(addr, fmt.Sprintf("%s/cert.pem", path), fmt.Sprintf("%s/key.pem", path))
		if err != nil {
			e.Logger.Info("shutting down the server")
		}
	}()

	// Wait for interrupt signal to gracefully shutdown the server with
	// a timeout of 10 seconds.
	sc := make(chan os.Signal)
	signal.Notify(sc,
		os.Kill,
		os.Interrupt,
		syscall.SIGHUP,
		syscall.SIGINT,
		syscall.SIGTERM,
		syscall.SIGQUIT)
	<-sc
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	defer func() {
		for _, m := range masters {
			if m.Status == 1 {
				m.Engine.Close()
			}
		}
	}()

	if err := e.Shutdown(ctx); err != nil {
		e.Logger.Fatal(err)
	}
}

func getRoles(uid uint) ([]string, error) {
	key := fmt.Sprintf("user-roles-%d", uid)
	if value, found := caches.Get(key); found {
		if rs, ok := value.([]string); ok {
			return rs, nil
		}
	}
	engine.Ping()
	rows, err := engine.QueryString("SELECT a.name FROM mm_roles a INNER JOIN mm_relations b ON a.role_id = b.descendant_id WHERE b.taxonomy_id = 1 AND b.ancestor_id = ?", uid)
	if err != nil {
		return nil, err
	}
	var roles []string
	for _, row := range rows {
		roles = append(roles, row["name"])
	}
	caches.Set(key, roles, cache.NoExpiration)
	return roles, nil
}

func getOptionalSoleRequestValue(values url.Values, key string, initial string) (string, error) {
	if value, found := values[key]; found {
		if len(value) == 1 {
			if len(value[0]) > 0 {
				return value[0], nil
			}
		}
	} else {
		return initial, nil
	}
	return "", errors.New("Bad Request")
}

func getRequiredSoleRequestValue(values url.Values, key string) (string, error) {
	if value, found := values[key]; found {
		if len(value) == 1 {
			if len(value[0]) > 0 {
				return value[0], nil
			}
		}
	}
	return "", errors.New("Bad Request")
}

// TODO: 测试函数，代码稳定后删除
func doHome(c echo.Context) error {
	// c.Param("name")      // 路由中的参数
	// c.FormValue("name")  // 表单中的参数
	// c.QueryParam("name") // 地址栏中的参数
	engine.Ping()
	engine.Where("name = ?", "xumiao").Get(&models.User{})
	return c.String(200, "")
}

func userLogin(c echo.Context) error {
	username := c.FormValue("user")
	password := c.FormValue("password")

	engine.Ping()
	var user models.User
	engine.Where("login = ?", username).Get(&user)
	if user.UserId > 0 {
		salt := []byte(cfg.Salt)
		h := sha512.New384()
		h.Write([]byte(password))
		h.Write(salt)
		if bytes.Compare(h.Sum(nil), user.Password) == 0 {
			// Create token
			token := jwt.New(jwt.SigningMethodHS256)

			// Set claims
			claims := token.Claims.(jwt.MapClaims)
			claims["id"] = user.UserId
			claims["login"] = user.Login
			claims["name"] = user.Name
			claims["exp"] = time.Now().Add(time.Hour * 24).Unix()

			// Generate encoded token and send it as response.
			signedString, err := token.SignedString([]byte(cfg.Secret))
			if err != nil {
				return err
			}

			roles, err := getRoles(user.UserId)
			if err != nil {
				return err
			}

			return c.JSON(200, map[string]interface{}{
				"token": signedString,
				"id":    user.UserId,
				"login": user.Login,
				"name":  user.Name,
				"roles": roles,
			})
		}
	}
	return echo.ErrUnauthorized
}

// func restricted(c echo.Context) error {
//     user := c.Get("user").(*jwt.Token)
//     claims := user.Claims.(jwt.MapClaims)
//     name := claims["name"].(string)
//     id := claims["id"].(uint)
//     return c.String(200, "Welcome "+name+"!")
//     return nil
// }
func tokenValidation(c echo.Context) error {
	claims, err := getClaims(c.FormValue("token"))
	if err == nil {
		id := uint(claims["id"].(float64))
		roles, err := getRoles(id)
		if err != nil {
			return err
		}
		return c.JSON(200, map[string]interface{}{"valid": true, "roles": roles})
	} else {
		return c.JSON(200, map[string]bool{"valid": false})
	}
}

func getMasters(userId uint) ([]*models.Master, error) {
	key := fmt.Sprintf("user-to-masters-%d", userId)
	if value, found := caches.Get(key); found {
		if rs, ok := value.([]*models.Master); ok {
			return rs, nil
		}
	}
	roles, err := getRoles(userId)
	if err == nil {
		var m = make([]*models.Master, 0)
		isAdmin := false
		for _, r := range roles {
			if r == "admin" {
				isAdmin = true
				break
			}
		}
		engine.Ping()
		if isAdmin {
			engine.Table("mm_masters").Find(&m)
		} else {
			engine.Table("mm_masters").
				Alias("m").
				Join("INNER", []string{"mm_relations", "r"}, "m.master_id = r.descendant_id AND r.taxonomy_id = 2").
				Where("m.status = 1 AND r.ancestor_id = ?", userId).
				Find(&m)
		}
		caches.Set(key, m, cache.NoExpiration)
		return m, nil
	}
	return nil, err
}

func showMasters(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		token := c.Get(JWT_ContextKey).(*jwt.Token)
		claims := token.Claims.(jwt.MapClaims)
		id := uint(claims["id"].(float64))
		if m, err := getMasters(id); err == nil {
			return c.JSON(200, m)
		}
		return echo.NewHTTPError(401, "Unauthorized")
	}
}

func showDatabases(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		mid, _ := strconv.ParseUint(c.Param("id"), 10, 32)
		token := c.Get(JWT_ContextKey).(*jwt.Token)
		claims := token.Claims.(jwt.MapClaims)
		uid := uint(claims["id"].(float64))
		var master *models.Master
		if m, err := getMasters(uid); err == nil {
			found := false
			for _, v := range m {
				if v.MasterId == uint(mid) {
					master = v
					found = true
					break
				}
			}
			if found {
				// TODO: here is model.Master, no method databases
				databases := master.GetDatabases()
				var (
					msg []byte
					err error
				)
				if cfg.Debug {
					msg, err = prettyJSON(databases)
				} else {
					msg, err = json.Marshal(databases)
				}
				if err == nil {
					if cfg.Debug {
						msg, _ = prettyJSON(databases)
					}
					return c.JSON(200, string(msg))
				}
			}
		}
		return echo.NewHTTPError(401, "Unauthorized")
	}
}

func showTables(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
		database := c.Param("database")

		found := false
		var m *Master
		for _, m = range masters {
			if m.MasterId == uint(id) {
				found = true
				break
			}
		}
		if !found {
			return c.String(200, "") // TODO:
		} else {
			tables, _ := m.tables(database)
			var (
				msg []byte
				err error
			)
			if cfg.Debug {
				msg, err = prettyJSON(tables)
			} else {
				msg, err = json.Marshal(tables)
			}
			if err != nil {
				return c.String(200, "") // TODO:
			} else {
				return c.JSON(200, string(msg))
			}
		}
	}
}

func showColumns(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
		database := c.Param("database")
		table := c.Param("table")

		found := false
		var m *Master
		for _, m = range masters {
			if m.MasterId == uint(id) {
				found = true
				break
			}
		}
		if !found {
			return c.String(200, "") // TODO:
		} else {
			columns, _ := m.columns(database, table)
			var (
				msg []byte
				err error
			)
			if cfg.Debug {
				msg, err = prettyJSON(columns)
			} else {
				msg, err = json.Marshal(columns)
			}
			if err != nil {
				return c.String(200, "") // TODO:
			} else {
				return c.JSON(200, string(msg))
			}
		}
	}
}

func showIndices(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
		database := c.Param("database")
		table := c.Param("table")

		found := false
		var m *Master
		for _, m = range masters {
			if m.MasterId == uint(id) {
				found = true
				break
			}
		}
		if !found {
			return c.String(200, "") // TODO:
		} else {
			indices, _ := m.indices(database, table)
			var (
				msg []byte
				err error
			)
			if cfg.Debug {
				msg, err = prettyJSON(indices)
			} else {
				msg, err = json.Marshal(indices)
			}
			if err != nil {
				return c.String(200, "") // TODO:
			} else {
				return c.JSON(200, string(msg))
			}
		}
	}
}

func prettyJSON(data interface{}) ([]byte, error) {
	buffer := new(bytes.Buffer)
	encoder := json.NewEncoder(buffer)
	encoder.SetIndent("", `	`)

	err := encoder.Encode(data)
	return buffer.Bytes(), err
}

func getClaims(ts string) (jwt.MapClaims, error) {
	token, err := jwt.Parse(ts, func(token *jwt.Token) (interface{}, error) {
		return []byte(cfg.Secret), nil
	})
	if err == nil {
		if token.Valid {
			claims := token.Claims.(jwt.MapClaims)
			return claims, nil
		} else {
			return nil, errors.New(fmt.Sprintf("string %s is not a valid token.", ts))
		}
	} else {
		return nil, err
	}
}

// 更新或者删除cache，更新统计
func update(c echo.Context) error {
	p := new(config.Payload)            //
	r := c.Request()                    //
	defer r.Body.Close()                //
	body, err := ioutil.ReadAll(r.Body) //

	if err != nil {
		fmt.Printf("an error occurred when attempt to read data, error: %+v\n", err)
		return nil
	}

	if err := json.Unmarshal([]byte(body), &p); err != nil {
		fmt.Printf("invalid request, error: %+v\n", err)
		return nil
	}
	// 只处理tickets库
	if p.Database != cfg.Database.Db {
		return c.JSON(200, "{\"msg\" : \"ignored\"}")
	}
	// var list []map[string]interface{}
	// data := p.Event.Data
	switch p.Event.EventType {
	case "insert", "delete":
		// list = append(list, data)
	case "update":
		// if _, found := data["new_data"]; found {
		// 	list = append(list, data["new_data"].(map[string]interface{}))
		// }
		// if _, found := data["old_data"]; found {
		// 	list = append(list, data["old_data"].(map[string]interface{}))
		// }
	case "create", "alter", "drop":
		return c.JSON(200, "{\"msg\" : \"ignored\"}")
	default:
	}
	return c.JSON(200, "{\"msg\" : \"pong\"}")
}
